/* 
 * Copyright (C) 2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Text;
using System.Text.RegularExpressions;

using DCSharp.Security.Cryptography;

namespace DCSharp
{
	/// <summary>
	/// A globally-unique user identifier.
	/// </summary>
	public struct Uid
	{
		public static readonly Uid Empty = new Uid(new byte[Length]);
		public const int Length = 24;

		private byte[] buffer;

		/// <summary>
		/// Constructs a new Uid from a specific string.
		/// </summary>
		/// <param name="uid">The byte value for the Uid, encoded in Base32.</param>
		public Uid(string uid) : this(Base32.Decode(uid))
		{
		}

		/// <summary>
		/// Constructs a new Uid from a specific byte array.
		/// </summary>
		/// <param name="buffer">The byte values for the Uid.</param>
		/// <remarks>The byte array must be 24 bytes long.</remarks>
		public Uid(byte[] buffer)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			else if (!IsValid(buffer))
			{
				throw new ArgumentException("buffer", "Invalid buffer length.");
			}
			this.buffer = buffer;
		}

		/// <summary>
		/// Generates a random Uid.
		/// </summary>
		/// <returns>A random Uid.</returns>
		public static Uid Generate()
		{
			return new Uid(Random());
		}

		/// <summary>
		/// Calculates a Client ID from a Private ID.
		/// </summary>
		/// <param name="pid">The PID.</param>
		/// <returns>The calculated Cid.</returns>
		public static Uid CalculateCid(Uid pid)
		{
			using (Tiger algorithm = Tiger.Create())
			{
				return new Uid(algorithm.ComputeHash(pid.ToByteArray()));
			}
		}

		/// <summary>
		/// Checks if the specified buffer can be used to create a new Uid.
		/// </summary>
		/// <param name="buffer">The buffer to validate.</param>
		/// <returns>True if the buffer is valid; otherwise, false.</returns>
		public static bool IsValid(byte[] buffer)
		{
			return buffer != null && buffer.Length == Length;
		}

		/// <summary>
		/// Checks if a specific string can be used to create a new Uid.
		/// </summary>
		/// <param name="uid">The string to validate."></param>
		/// <returns>True if the string is valid; otherwise, false.</returns>
		public static bool IsValid(string uid)
		{
			return uid != null && Regex.IsMatch(uid, "^[A-Za-z2-7]{39}$");
		}

		/// <summary>
		/// Gets the value of the Uid as a byte array.
		/// </summary>
		/// <returns>The byte values of the Uid.</returns>
		public byte[] ToByteArray()
		{
			return (byte[])buffer.Clone();
		}

		public static bool operator==(Uid a, Uid b)
		{
			for (int i = 0; i < Length; i++)
			{
				if (a.buffer[i] != b.buffer[i])
				{
					return false;
				}
			}
			return true;
		}

		public static bool operator!=(Uid a, Uid b)
		{
			return !(a == b);
		}

		public static implicit operator string(Uid a)
		{
			return a.ToString();
		}

		public static explicit operator Uid(string a)
		{
			return new Uid(a);
		}

		public override bool Equals(object value)
		{
			if (value == null)
			{
				return false;
			}
			if (GetType() == value.GetType())
			{
				return this == (Uid)value;
			}
			return false;
		}

		public override int GetHashCode()
		{
			return BitConverter.ToInt32(buffer, 0);
		}

		public override string ToString()
		{
			return Base32.Encode(buffer);
		}

		private static byte[] Random()
		{
			byte[] buffer = new byte[Length];
			new Random().NextBytes(buffer);

			return buffer;
		}
	}
}
